package org.dtrd.config.wechat.api.impl;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dtrd.common.util.DateUtils;
import org.dtrd.common.util.IPUtils;
import org.dtrd.config.pay.PayConfig;
import org.dtrd.config.wechat.config.officialaccount.WechatTemplateConfig.*;
import org.dtrd.config.wechat.config.wechatpay.PayScene;
import org.dtrd.config.wechat.config.officialaccount.WechatTemplateConfig;
import org.dtrd.config.wechat.config.WechatUtil;
import org.dtrd.config.wechat.entity.bean.*;
import org.dtrd.config.wechat.entity.bean.miniprogram.CloudBase;
import org.dtrd.config.wechat.entity.bean.miniprogram.LineColor;
import org.dtrd.config.wechat.entity.bean.miniprogram.WxaInfo;
import org.dtrd.config.wechat.entity.bean.officialaccount.*;
import org.dtrd.config.wechat.entity.bean.officialaccount.template.*;
import org.dtrd.config.wechat.entity.bean.officialaccount.template.ServiceStateMsg.ServiceStateMsgBuilder;
import org.dtrd.config.wechat.entity.request.wechatpay.SceneInfo;
import org.dtrd.config.wechat.entity.response.WechatResponse;
import org.dtrd.config.wechat.entity.response.WxCreateIndividuationResponse;
import org.dtrd.config.wechat.entity.response.WxTagListResponse;
import org.dtrd.config.wechat.entity.response.WxUnionidResp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author xiao
 * @date 2022/3/18 10:02 PM
 */
@Component
@Slf4j
public class WechatApi {

    @Autowired
    private WechatMiniprogramApi wechatMiniprogramApi;

    @Autowired
    @Lazy
    private WechatPayApi wechatPayApi;

    @Autowired
    private WechatOfficialAccountApi wechatOfficialAccountApi;

    /**
     * 获取微信Access Token
     *
     * @param appid  小程序appid
     * @param secret 小程序AccessSecret
     * @return
     */
    public AccessToken getAccessToken(String appid, String secret) {
        AccessToken accessToken;
//        Object accessTokenObj = redisUtil.get(appid + secret);
//        if (accessTokenObj == null) {
//            if (accessToken != null) {
//                redisUtil.set(appid + secret, accessToken.getAccessToken(), accessToken.getExpiresIn());
//            }
//        } else {
//            accessToken = new AccessToken();
//            accessToken.setAccessToken(StrUtil.str(accessTokenObj, StandardCharsets.UTF_8));
//        }
        accessToken = wechatMiniprogramApi.getAccessToken(appid, secret);
        log.info("WechatApi.getAccessToken.[appid, secret]  accessToken=" + accessToken);
        return accessToken;
    }

    public String getRidInfo(WechatRidRequest request) {
        return wechatOfficialAccountApi.getRidInfo(getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret()), request);
    }

    //region================================  小程序相关接口 开始  ================================

    /**
     * 小程序登录凭证校验
     *
     * @param appId           小程序Appid
     * @param accessKeySecret 小程序AccessSecret
     * @param jsCode
     * @return
     * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html">code2Session</a>
     */
    public JSONObject code2Session(String appId, String accessKeySecret, String jsCode) {
        return wechatMiniprogramApi.code2Session(appId, accessKeySecret, jsCode);
    }

    /**
     * 微信小程序用户数据解密
     *
     * @param sessionKey
     * @param encryptedData
     * @param rawData
     * @param iv
     * @param signature
     * @return
     * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/signature.html">数据解密</a>
     */
    public JSONObject decryptInfo(String sessionKey, String encryptedData, String rawData, String iv, String signature) {
        log.info("WechatApi.decryptInfo.[sessionKey = {}, encryptedData={}, rawData={}, iv={}, signature={}", sessionKey, encryptedData, rawData, iv, signature);
        return wechatMiniprogramApi.decryptInfo(sessionKey, encryptedData, rawData, iv, signature);
    }

    /**
     * 创建小程序二维码
     *
     * @param path
     * @param width
     * @return
     */
    public String createMiniProgramQRCode(String path, int width) {
        AccessToken accessToken = getAccessToken(WechatUtil.getClientId(), WechatUtil.getAccessKeySecret());
        if (accessToken != null) {
            return wechatMiniprogramApi.createQRCode(accessToken.getAccessToken(), path, width);
        } else {
            return null;
        }
    }

    /**
     * 生成已发布小程序码
     *
     * @param path       页面路径
     * @param envVersion 要打开的小程序版本。{@link org.dtrd.config.wechat.config.WechatConstant.MiniprogramVersion}
     * @param width      二维码宽度  最小 280px，最大 1280px
     * @param autoColor  自动配置线条颜色，如果颜色依然是黑色，则说明不建议配置主色调  默认false
     * @param lineColor  autoColor 为 false 时生效，使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} 十进制表示
     * @param isHyaline  是否需要透明底色 默认 false
     * @return
     * @see <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.get.html">getwxacode</a>
     */
    public String getWxacode(String path, String envVersion, int width, boolean autoColor, LineColor lineColor, boolean isHyaline) {
        AccessToken accessToken = getAccessToken(WechatUtil.getClientId(), WechatUtil.getAccessKeySecret());
        if (accessToken != null) {
            return wechatMiniprogramApi.getWxacode(accessToken.getAccessToken(), path, envVersion, width, autoColor, lineColor, isHyaline);
        } else {
            return null;
        }
    }

    /**
     * 生成已发布的小程序码，适用于需要的码数量极多的业务场景
     *
     * @param scene
     * @param page       页面
     * @param checkPath  检查page是否存在：true为已经发布的小程序存在的页面；false时允许小程序未发布或者page不存在
     * @param envVersion 要打开的小程序的版本
     * @param width      二维码的宽度，单位 px，最小 280px，最大 1280px
     * @param autoColor  自动配置线条颜色，如果颜色依然是黑色，则说明不建议配置主色调，默认 false
     * @param lineColor  auto_color 为 false 时生效，使用 rgb 设置颜色，十进制表示
     * @param isHyaline  是否需要透明底色，为 true 时，生成透明底色的小程序
     * @return
     */
    public String getUnlimitedWxacode(String scene, String page, boolean checkPath, String envVersion, int width, boolean autoColor, LineColor lineColor, boolean isHyaline) {
        AccessToken accessToken = getAccessToken(WechatUtil.getClientId(), WechatUtil.getAccessKeySecret());
        if (accessToken != null) {
            return wechatMiniprogramApi.getUnlimitWxacode(accessToken.getAccessToken(), scene, page, checkPath, envVersion, width, autoColor, lineColor, isHyaline);
        } else {
            return null;
        }
    }

    /**
     * 获取小程序 scheme 码，适用于短信、邮件、外部网页、微信内等拉起小程序的业务场景
     *
     * @param jumpWxa        跳转到的目标小程序信息
     * @param expireType     到期失效的 scheme 码失效类型，失效时间：0，失效间隔天数：1
     * @param expireTime     expire_type 传值为 0，需注意 expire_time 传值的时间戳不超过 30 天，即该参数最长传值有效期为 30 天；
     * @param expireInterval expire_type 传值为 1，需注意 expire_interval 传值范围为 [1, 30]，即该参数最长传值间隔天数为 30
     * @return
     */
    public String generateScheme(boolean isExpire, WxaInfo jumpWxa, int expireType, int expireTime, int expireInterval) {
        AccessToken accessToken = getAccessToken(WechatUtil.getClientId(), WechatUtil.getAccessKeySecret());
        if (accessToken != null) {
            return wechatMiniprogramApi.generateScheme(accessToken.getAccessToken(), isExpire, jumpWxa, expireType, expireTime, expireInterval);
        } else {
            return null;
        }
    }

    /**
     * 查询小程序 scheme 码，及长期有效 quota
     *
     * @param scheme 小程序 scheme 码
     * @return
     */
    public String queryScheme(String scheme) {
        AccessToken accessToken = getAccessToken(WechatUtil.getAccessKeySecret(), WechatUtil.getAccessKeySecret());
        if (accessToken != null) {
            return wechatMiniprogramApi.queryScheme(accessToken.getAccessToken(), scheme);
        } else {
            return null;
        }
    }

    /**
     * 获取小程序 URL Link，适用于短信、邮件、网页、微信内等拉起小程序的业务场景。
     *
     * @param path           通过 URL Link 进入的小程序页面路径，必须是已经发布的小程序存在的页面，不可携带 query 。path 为空时会跳转小程序主页
     * @param query          通过 URL Link 进入小程序时的query，最大1024个字符
     * @param envVersion     要打开的小程序版本
     * @param isExpire       生成的 URL Link 类型，到期失效：true，永久有效：false
     * @param expireType     小程序 URL Link 失效类型，失效时间：0，失效间隔天数：1
     * @param expireTime     到期失效的 URL Link 的失效时间，为 Unix 时间戳
     * @param expireInterval 到期失效的URL Link的失效间隔天数
     * @param cloudBase      云开发静态网站自定义 H5 配置参数，可配置中转的云开发 H5 页面,不填默认用官方 H5 页面
     * @return
     */
    public String generateLink(String path, String query, String envVersion, boolean isExpire, int expireType, int expireTime, int expireInterval, CloudBase cloudBase) {
        AccessToken accessToken = getAccessToken(WechatUtil.getClientId(), WechatUtil.getAccessKeySecret());
        if (accessToken != null) {
            return wechatMiniprogramApi.generateLink(accessToken.getAccessToken(), path, query, envVersion, isExpire, expireType, expireTime, expireInterval, cloudBase);
        } else {
            return null;
        }
    }

    /**
     * 查询小程序 url_link 配置，及长期有效 quota
     *
     * @param urlLink 小程序url_link
     * @return
     */
    public String queryLink(String urlLink) {
        AccessToken accessToken = getAccessToken(WechatUtil.getClientId(), WechatUtil.getAccessKeySecret());
        if (accessToken != null) {
            return wechatMiniprogramApi.queryLink(accessToken.getAccessToken(), urlLink);
        } else {
            return null;
        }
    }

    //endregion================================  小程序相关接口 结束  ================================

    //region================================  公众号相关接口 开始  ================================

    /**
     * 获取公众号微信token
     *
     * @return
     */
    public String getOfficialAccountAccessToken() {
        AccessToken accessToken = getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret());
        return accessToken != null ? accessToken.getAccessToken() : null;
    }

    public WxUnionidResp getUnionid(String openid) {
        return wechatOfficialAccountApi.getUnionid(getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret()).getAccessToken(), openid);
    }

    public WechatOfficialAccountUserInfo getUserInfo(String accessToken, String openid) {
        return wechatOfficialAccountApi.getUserInfo(accessToken, openid);
    }

    /**
     * 校验 access_token 是否有效
     *
     * @param token  待校验的 access_token
     * @param openid openid
     * @return 校验结果
     */
    public boolean checkOfficialAccountAccessToken(String token, String openid) {
        return wechatOfficialAccountApi.checkOfficialAccountAccessToken(token, openid);
    }

    public OfficialAccountAccessToken getOfficialAccountAuthorizeAccessToken(String code) {
        return wechatOfficialAccountApi.getOfficialAccountAccessToken(code);
    }

    public OfficialAccountAccessToken refreshOfficialAccountAccessToken(String refreshToken) {
        return wechatOfficialAccountApi.refreshOfficialAccountAccessToken(refreshToken);
    }

    public boolean checkSignature(String signature, String timestamp, String nonce, String token) {
        return wechatOfficialAccountApi.checkSignature(signature, timestamp, nonce, token);
    }

    public ApiTicket getApiTicket(String accessToken) {
        return wechatOfficialAccountApi.getApiTicket(accessToken);
    }

    public String doWechatSign(String apiTicket, String pageUrl, String noncestr, long timestamp) {
        return wechatOfficialAccountApi.doWechatSign(apiTicket, pageUrl, noncestr, timestamp);
    }

    // region ==============================  模板消息  ======================================================
    public void sendTemplateMsg(String accessToken, Map params) {
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, params);
    }

    public String getTemplateList() {
        AccessToken accessToken = getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret());
        return wechatOfficialAccountApi.getTemplateList(accessToken.getAccessToken());
    }

    /**
     * 发送用户创建订单消息
     * "{{first.DATA}}
     * 订单类型：{{keyword1.DATA}}
     * 下单人员：{{keyword2.DATA}}
     * 下单电话：{{keyword3.DATA}}
     * 客户电话：{{keyword4.DATA}}
     * 客户地址：{{keyword5.DATA}}
     * {{remark.DATA}}"
     * "你有一个新的订单
     * 订单类型：救援服务
     * 下单人员：张三
     * 下单电话：13888888888
     * 客户电话：18888888888
     * 客户地址：鼓楼区省体育中心
     * 点击查看订单详情"
     */
    public void sendUserCreateOrder(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateOrder.USER_CREATE_ORDER.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 收到下单通知
     * "
     * {{first.DATA}}
     * 订单号：{{keyword1.DATA}}
     * 商品名称：{{keyword2.DATA}}
     * 订单类型：{{keyword3.DATA}}
     * 支付时间：{{keyword4.DATA}}
     * 负责人：{{keyword5.DATA}}
     * {{remark.DATA}}"	"你收到一个新订单，请立即处理！
     * 订单号：BB2019030412110123
     * 商品名称：极速开票标准版
     * 订单类型：腾讯云
     * 支付时间：2019-03-04 12:11:01
     * 负责人：王小二
     * 谢谢！"
     */
    public void receiveOrder(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateOrder.RECEIVED_ORDER.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 订单状态更新提醒
     * "
     * {{first.DATA}}
     * 更新时间：{{keyword1.DATA}}
     * 订单类型：{{keyword2.DATA}}
     * 订单状态：{{keyword3.DATA}}
     * 订单来源：{{keyword4.DATA}}
     * 订单详情：{{keyword5.DATA}}
     * {{remark.DATA}}
     * 订单状态更新提醒
     * 更新时间：2014年11月5日 9点03分
     * 订单类型：采购订单
     * 订单状态：已审核通过
     * 订单来源：上海客悦软件有限公司
     * 订单详情：联想笔记本（型号）X1
     * 订单已通过审核，如有疑问请致电。
     * "
     */
    public void statusUpdate(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateOrder.ORDER_STATUS_UPDATE.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 订单完成通知
     * <p>
     * {{first.DATA}}
     * 订单流水号：{{keyword1.DATA}}
     * 订单编号：{{keyword2.DATA}}
     * 金额：{{keyword3.DATA}}
     * 下单时间：{{keyword4.DATA}}
     * {{remark.DATA}}
     * 尊敬的客户你好，你的订单商家已成功接单。
     * 订单流水号：wx-02220
     * 订单编号：wx12312121212131
     * 金额：16.8元
     * 下单时间：2018-01-10 09:11:28
     * 点击详情，查看订单更多详情信息。
     */
    public void orderComplete(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateOrder.ORDER_COMPLETE.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 工作计划提醒
     * <p>
     * {{first.DATA}}
     * 计划名称：{{keyword1.DATA}}
     * 计划时间：{{keyword2.DATA}}
     * 发布人：{{keyword3.DATA}}
     * 责任人：{{keyword4.DATA}}
     * 检视人：{{keyword5.DATA}}
     * {{remark.DATA}}
     * 您有一个周计划
     * 计划名称：登录界面开发计划
     * 计划时间：17-11-01~17-11-05
     * 发布人：iFeng
     * 责任人：张三
     * 检视人：李四
     * 如有问题请联系管理员
     */
    public void planRemind(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplatePlan.PLAN_REMIND.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 服务进度提醒
     * <p>
     * {{first.DATA}}
     * 服务类型：{{keyword1.DATA}}
     * 服务状态：{{keyword2.DATA}}
     * 服务时间：{{keyword3.DATA}}
     * {{remark.DATA}}
     * 亲爱的用户，您的服务状态发生变更
     * 服务类型：申请虚拟主机
     * 服务状态：成功申请
     * 服务时间：2016/12/26——2017/12/25
     * 请在后台当前状态下提交下一阶段资料！
     */
    public void serviceProcess(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateService.SERVICE_PROCESS.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 提现驳回提醒
     * <p>
     * {{first.DATA}}
     * 提现时间：{{keyword1.DATA}}
     * 提现金额：{{keyword2.DATA}}
     * 驳回原因：{{keyword3.DATA}}
     * {{remark.DATA}}
     * 您好，您申请的信息不符
     * 提现时间：2014年7月21日
     * 提现金额：100元
     * 驳回原因：信息不符
     * 感谢你的使用。
     */
    public void withdrawReject(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateWithdraw.WITHDRAW_REJECT.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 提现失败提醒
     * <p>
     * {{first.DATA}}
     * 提现金额：{{keyword1.DATA}}
     * 提现时间：{{keyword2.DATA}}
     * 失败原因：{{keyword3.DATA}}
     * {{remark.DATA}}
     * 张三您好，您在我微信号的提现失败了
     * 提现金额：20元
     * 提现时间：2017年1月5日 18:18
     * 失败原因：输入的名称不正确
     * 请您按照失败原因修改相关信息后，重新提现！
     */
    public void withdrawFailure(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateWithdraw.WITHDRAW_FAILURE.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 医嘱提醒
     * <p>
     * {{first.DATA}}
     * 医生姓名：{{keyword1.DATA}}
     * 医院名称：{{keyword2.DATA}}
     * 医嘱内容：{{keyword3.DATA}}
     * {{remark.DATA}}
     * 您好 小李
     * 医生：李海霞
     * 医院：北京医院
     * 医嘱：明天上午10点复诊
     * 点击详情，查看预约
     */
    public void doctorReminder(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateMedicine.DOCTOR_REMINDER.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 服药提醒
     * <p>
     * {{first.DATA}}
     * 提醒事项：{{keyword1.DATA}}
     * 提醒时间：{{keyword2.DATA}}
     * 主治医生：{{keyword3.DATA}}
     * 医生电话：{{keyword4.DATA}}
     * {{remark.DATA}}
     * 您好，以下是您定制的服药提醒通知
     * 提醒事项：今日服药时间已到
     * 提醒时间：2016年8月9日 08:00
     * 主治医生：温乐梅
     * 医生电话：0755-21507199
     * 接到通知请马上服药，如身体不适请联系您的医务人员，感谢您的使用！
     */
    public void medicineReminder(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateMedicine.MEDICINE_REMINDER.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 发送打卡事件提醒
     * "陈总基地柑橘防冻措施已完成
     * 方案名称：陈总基地柑橘种植方案
     * 执行时间：2018-02-28
     * 执行人：张文
     * 事件类型：植保
     * 所有果树已经包扎，部分进行了修剪"
     *
     * @param url
     * @param
     */
    public void sendPlanExecution(String accessToken, String openId, String url, WechatTemplateParams data, String pagePath) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram(pagePath, WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplatePlan.PLAN_EXECUTION.getTemplateId(), url, miniProgram, data, null);
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 预约成功通知
     * <p>
     * {{first.DATA}}
     * 服务类别：{{keyword1.DATA}}
     * 处理状态：{{keyword2.DATA}}
     * 提交时间：{{keyword3.DATA}}
     * {{remark.DATA}}
     * 恭喜！您预约已成功啦！
     * 服务类别：电话咨询
     * 处理状态：待付款
     * 提交时间：2018-08-03 15:48
     * <p>
     * <p>
     * 您预约的时间最终确定为2018-08-03 16:47，点击进入马上付款。
     * 您的患者XXX复诊预约成功
     * 服务类别：复诊预约
     * 处理状态：已成功
     * 提交时间：2018-08-03 15:48
     * 您预约的时间最终确定为2018-08-03 16:47，点击查看复诊详情。
     */
    public void appointmentSucceed(@Nonnull ServiceStateMsg msg, @Nonnull String... receiverOpenIds) {
        TemplateOthers template = TemplateOthers.APPOINTMENT_SUCCEED;
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getServiceType())
                .withKeyword2(msg.getHandleState())
                .withKeyword3(msg.getMsgTime())
                .withRemark(msg.getRemark()).build();
        sendTemplateMsg(template.getTemplateId(), msg.getUrl(), data, receiverOpenIds);
    }

    /**
     * 新增建议
     * <p>
     * {{first.DATA}}
     * 意见内容：{{keyword1.DATA}}
     * 时间：{{keyword2.DATA}}
     * {{remark.DATA}}
     * 医生为您的饮食提出了建议，请及时查看！
     * 意见内容：【取建议内容】
     * 时间：2014年7月31日
     * 点击查看详情
     */
    public void addSuggest(@Nonnull SuggestMsg msg, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getSuggestContent())
                .withKeyword2(msg.getMsgTime())
                .withRemark(msg.getRemark()).build();
        sendTemplateMsgToXcx(TemplatePlan.PLAN_DIET_SUGGEST.getTemplateId(), msg.getMiniProgram(), data, receiverOpenIds);
    }

    /**
     * 打卡成功（患者端）
     *
     * @param msg
     * @param receiverOpenIds <p>
     *                        {{first.DATA}}
     *                        打卡主题：{{keyword1.DATA}}
     *                        打卡状态：{{keyword2.DATA}}
     *                        打卡时间：{{keyword3.DATA}}
     *                        {{remark.DATA}}
     *                        </p>
     *                        我们已经收到您的打卡了，非常棒！
     *                        打卡主题：打卡项目
     *                        打卡状态：已打卡
     *                        打卡时间：2020/02/21 10:10
     *                        请耐心等待医师团队的反馈，请继续坚持打卡
     */
    public void signinSuccess(SigninSuccessMsg msg, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getSigninName())
                .withKeyword2(msg.getSigninState())
                .withKeyword3(msg.getSigninTime())
                .withRemark(msg.getRemark()).build();
        sendTemplateMsg(TemplatePlan.PLAN_DIET_PATIENT.getTemplateId(), null, data, receiverOpenIds);
    }

    /**
     * 打卡成功（医生端）
     *
     * @param msg
     * @param receiverOpenIds <P>
     *                        {{first.DATA}}
     *                        姓名：{{keyword1.DATA}}
     *                        时间：{{keyword2.DATA}}
     *                        状态：{{keyword3.DATA}}
     *                        {{remark.DATA}}
     *                        </p>
     *
     *                        患者已完成饮食打卡，请及时反馈建议及评分
     *                        姓名：患者姓名
     *                        时间：2018-01-15 14:02:48
     *                        状态：打卡成功
     *                        点击查看打卡详情
     */
    public void signinSuccessForDoctor(SigninMissMsg msg, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getPatientName())
                .withKeyword2(msg.getSigninTime())
                .withKeyword3(msg.getSigninstate())
                .withRemark(msg.getRemark()).build();
        sendTemplateMsg(TemplatePlan.PLAN_DIET_DOCTOR.getTemplateId(), msg.getUrl(), data, receiverOpenIds);
    }

    /**
     * 漏打卡
     *
     * @param msg
     * @param receiverOpenIds <P>
     *                        {{first.DATA}}
     *                        姓名：{{keyword1.DATA}}
     *                        时间：{{keyword2.DATA}}
     *                        状态：{{keyword3.DATA}}
     *                        {{remark.DATA}}
     *                        </p>
     *
     *                        患者未按时完成打卡，请及时关注患者情况并在后台完成补卡
     *                        姓名：患者姓名
     *                        时间：漏打的打卡项目
     *                        状态：未打卡
     *                        点击与患者联系
     */
    public void signinMiss(SigninMissMsg msg, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getPatientName())
                .withKeyword2(msg.getSigninName())
                .withKeyword3(msg.getSigninstate())
                .withRemark(msg.getRemark()).build();
        sendTemplateMsg(TemplatePlan.PLAN_DIET_DOCTOR.getTemplateId(), msg.getUrl(), data, receiverOpenIds);
    }

    /**
     * 血糖异常消息
     * @param msg
     * @param receiverOpenIds
     * {{first.DATA}}
     * 打卡项目：{{keyword1.DATA}}
     * 打卡时间：{{keyword2.DATA}}
     * 血糖值：{{keyword3.DATA}}
     * 血糖波动幅度：{{keyword4.DATA}}
     * 血糖分析：{{keyword5.DATA}}
     * {{remark.DATA}}
     */
    public void abnormalSugarValue(AbnormalSugarMsg msg, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getTypeName())
                .withKeyword2(msg.getSigninTime())
                .withKeyword3(msg.getSugarValue() + "mmol/L")
                .withKeyword4(msg.getWaveValue() + "mmol/L")
                .withKeyword5(msg.getMsg())
                .withRemark(msg.getRemark()).build();
        sendTemplateMsg(TemplatePlan.ABNORMAL_SUGAR_VALUE.getTemplateId(), msg.getUrl(), data, receiverOpenIds);
    }

    /**
     * 执行异常提醒
     * <p>
     * {{first.DATA}}
     * 作业名称：{{keyword1.DATA}}
     * 当前状态：{{keyword2.DATA}}
     * 计划开始时间：{{keyword3.DATA}}
     * 计划结束时间：{{keyword4.DATA}}
     * 实际开始时间：{{keyword5.DATA}}
     * {{remark.DATA}}
     * 系统作业异常提醒
     * 作业名称：集中处理系统作业异常
     * 当前状态：执行成功
     * 计划开始时间：2016年9月26日 9:00
     * 计划结束时间：2016年9月26日 9:10
     * 实际开始时间：2016年9月26日 9:30
     * 请查明原因及时处理。
     */
    public void runtimeException(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateOthers.RUNTIME_EXCEPTION.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 执行结果通知
     * <p>
     * {{first.DATA}}
     * 作业执行结果：{{keyword1.DATA}}
     * 计划开始时间：{{keyword2.DATA}}
     * 实际开始时间：{{keyword3.DATA}}
     * 实际耗时：{{keyword4.DATA}}
     * 返回消息：{{keyword5.DATA}}
     * {{remark.DATA}}
     * 收到一条作业执行结果通知
     * 作业执行结果：主机批量作业执行成功
     * 计划开始时间：2016年9月26日 10:00
     * 实际开始时间：2016年9月26日 10:00
     * 实际耗时：170秒
     * 返回消息：success
     * 请及时关注作业运行状态。
     */
    public void runtimeConclusion(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateOthers.RUNTIME_CONCLUSION.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 系统异常提醒"
     * <p>
     * {{first.DATA}}
     * 项目名称：{{keyword1.DATA}}
     * 异常类型：{{keyword2.DATA}}
     * 异常定位：{{keyword3.DATA}}
     * 异常消息：{{keyword4.DATA}}
     * 发生时间：{{keyword5.DATA}}
     * {{remark.DATA}}
     * 您好，你负责的项目出现异常！
     * 项目名称：通关系统
     * 异常类型：CLIException
     * 异常定位：Resquest.php in line 122
     * 异常消息：数据库连接超时！
     * 发生时间：2017-03-17 14:49
     * 请落实异常情况！
     */
    public void systemException(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateOthers.SYSTEM_EXCEPTION.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 审核通过提醒
     * <p>
     * {{first.DATA}}
     * 所属部门：{{keyword1.DATA}}
     * 所属角色：{{keyword2.DATA}}
     * {{remark.DATA}}
     * 你注册的用户已通过审核
     * 所属部门：物资组
     * 所属角色：管理员
     * 点击进入平台
     */
    public void auditingPassing(String accessToken, String openId, String url, WechatTemplateParams data) {
        WechatTemplateMiniProgram miniProgram = new WechatTemplateMiniProgram("", WechatUtil.getClientId());
        WechatTemplate template = new WechatTemplate(openId, WechatTemplateConfig.TemplateRegister.AUDITING_INNER_SUCCESS.getTemplateId(), url, miniProgram, data, "#ff0000");
        wechatOfficialAccountApi.sendTemplateMsg(accessToken, template);
    }

    /**
     * 给医生发送患者注册/后台添加成功通知
     *
     * @param patientName     患者姓名
     * @param msgTime         注册或添加时间  yyyy年MM月dd日 HH:mm
     * @param inviter         邀请人(扫码医生姓名或者后天添加人姓名)
     * @param url             打开的链接
     * @param receiverOpenIds 接收人微信公众号OpenId
     */
    public void sendPatientRegisterMsg(@NotNull String patientName,
                                       @Nonnull String msgTime,
                                       @Nonnull String inviter,
                                       @Nonnull String url,
                                       @Nonnull String... receiverOpenIds) {
        if (patientName == null) {
            patientName = "匿名";
        }
        TemplateRegister patientRegister = TemplateRegister.PATIENT_REGISTER;
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(String.format(patientRegister.getDefaultTitleTemplate(), patientName))
                .withKeyword1(patientName)
                .withKeyword2(msgTime)
                .withKeyword3(inviter)
                .withRemark("点击立即联系患者").build();
        sendTemplateMsg(patientRegister.getTemplateId(), url, data, receiverOpenIds);
    }

    /**
     * 给患者发送绑定医生成功通知
     *
     * @param doctorName      医生姓名
     * @param url             打开的链接
     * @param receiverOpenIds 接收人微信公众号OpenId
     */
    public void sendBindDoctorMsg(@NotNull String doctorName,
                                  @Nonnull String url,
                                  @Nonnull String... receiverOpenIds) {
        ServiceStateMsg msg = ServiceStateMsgBuilder.singleInstance().withMsgTitle("您已成功成为" + doctorName + "工作室的患者")
                .withServiceType("绑定医生")
                .withHandleState("成功")
                .withMsgTime(DateUtils.formatDateTime())
                .withRemark("点击立即联系医生")
                .withUrl(url).build();
        sendTemplateService(msg, receiverOpenIds);
//        sendTemplateService("您已成功成为" + doctorName + "工作室的患者", "绑定医生", "成功", DateUtils.formatDateTime(),
//                "点击立即联系医生", url, receiverOpenIds);
    }

    public void sendTemplateService(@NotNull String msgTitle,
                                    @Nonnull String serviceType,
                                    @Nonnull String serviceState,
                                    @Nonnull String msgTime,
                                    @Nonnull String remark,
                                    @Nonnull String url,
                                    @Nonnull String... receiverOpenIds) {
        TemplateService template = TemplateService.SERVICE_PROCESS;
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msgTitle)
                .withKeyword1(serviceType)
                .withKeyword2(serviceState)
                .withKeyword3(msgTime)
                .withRemark(remark).build();
        sendTemplateMsg(template.getTemplateId(), url, data, receiverOpenIds);
    }

    public void sendTemplateService(@Nonnull ServiceStateMsg msg, @Nonnull String... receiverOpenIds) {
        TemplateService template = TemplateService.SERVICE_PROCESS;
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getServiceType())
                .withKeyword2(msg.getHandleState())
                .withKeyword3(msg.getMsgTime())
                .withRemark(msg.getRemark()).build();
        sendTemplateMsg(template.getTemplateId(), msg.getUrl(), data, receiverOpenIds);
    }


    /**
     * 订单支付成功：血糖仪购买成功/服务购买成功
     *
     * @param msgTitle        消息标题 患者支付成功：您的患者曾兰芳成功支付1888元
     * @param orderTitle      订单标题 xx血糖仪、xx服务
     * @param orderSummary    订单金额  :1000元
     * @param msgTime         订单时间  ：yyyy年MM月dd日 HH:mm
     * @param remark          备注
     * @param url             查看详情
     * @param receiverOpenIds 接收人微信公众号OpenId
     * @deprecated use sendOrderPaySuccessMsg instead  肖 2022-08-03 10:38:12
     */
    public void sendPaySuccessMsg(@NotNull String msgTitle, @Nonnull String orderTitle, @Nonnull String msgTime, @Nonnull String orderSummary, @Nonnull String remark, @Nullable String url, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msgTitle)
                .withKeyword1(orderTitle)
                .withKeyword3(orderSummary)
                .withKeyword4(msgTime).withRemark(remark).build();
        sendTemplateMsg(TemplateOrder.ORDER_PAY_SUCCESS.getTemplateId(), url, data, receiverOpenIds);
    }


    /**
     * 订单支付成功消息
     * 患者支付成功：您的患者曾兰芳成功支付299元
     * 订单标题：XX血糖仪/试纸XX套/以旧换新
     * 订单金额：299元
     * 支付时间：2020-04-20 10:00
     * 点击向患者发送消息
     *
     * @param msg
     * @param receiverOpenIds 接收人微信公众号OpenId
     */
    public void sendOrderPaySuccessMsg(@NotNull OrderStateMsg msg, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getOrderTitle())
                .withKeyword2(msg.getOrderNo())
                .withKeyword3(msg.getOrderSum())
                .withKeyword4(msg.getMsgTime()).withRemark(msg.getRemark()).build();
        sendTemplateMsg(TemplateOrder.ORDER_PAY_SUCCESS.getTemplateId(), msg.getUrl(), data, receiverOpenIds);
    }

    /**
     * 订单支付成功消息
     * 您参与的能力验证计划样品已发出，请保持手机畅通，以便快递及时联系您！
     * 收货人：黄伟文
     * 收货人手机号：18743615684
     * 快递公司：顺丰快递
     * 快递单号：234223423434
     * 您可点击详情确认收货，或自行登录确认。
     *
     * @param msg
     * @param receiverOpenIds 接收人微信公众号OpenId
     */
    public void sendOrderDeliveryMsg(@NotNull OrderDeliveryMsg msg, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msg.getMsgTitle())
                .withKeyword1(msg.getContactPerson())
                .withKeyword2(msg.getContactPhone())
                .withKeyword3(msg.getLogisticsName())
                .withKeyword4(msg.getLogisticsNo())
                .withRemark(msg.getRemark()).build();
        sendTemplateMsg(TemplateOrder.ORDER_IS_DELIVERY.getTemplateId(), msg.getUrl(), data, receiverOpenIds);
    }

    /**
     * 注册申请审核结果通知：发送账号信息
     *
     * @param msgTitle        消息标题
     * @param accountUser     姓名
     * @param account         账户
     * @param mobile          手机
     * @param workUnit        单位
     * @param roleName        角色
     * @param remark          备注
     * @param url             查看详情
     * @param receiverOpenIds 接收人微信公众号OpenId
     *                        <p>
     *                        {{first.DATA}}
     *                        姓名：{{keyword1.DATA}}
     *                        账号：{{keyword2.DATA}}
     *                        手机：{{keyword3.DATA}}
     *                        单位：{{keyword4.DATA}}
     *                        角色：{{keyword5.DATA}}
     *                        {{remark.DATA}}
     */
    public void sendAccountAuditMsg(@NotNull String msgTitle, @Nonnull String accountUser, @Nullable String account,
                                    @Nonnull String mobile, @Nullable String workUnit, @Nullable String roleName, @Nonnull String remark, @Nullable String url, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msgTitle)
                .withKeyword1(accountUser)
                .withKeyword2(account)
                .withKeyword3(mobile)
                .withKeyword4(workUnit)
                .withKeyword5(roleName)
                .withRemark(remark).build();
        sendTemplateMsg(TemplateRegister.AUDITING_SYSTEM_SUCCESS.getTemplateId(), url, data, receiverOpenIds);
    }

    /**
     * 绑定成功通知：绑定血糖仪成功
     *
     * @param msgTitle        消息标题
     * @param deviceSn        设备编号
     * @param msgTime         绑定时间 yyyy年MM月dd日 HH:mm
     * @param receiverOpenIds 接收人微信公众号OpenId
     *                        <p>
     *                        {{first.DATA}}
     *                        设备编号：{{keyword1.DATA}}
     *                        绑定时间：{{keyword2.DATA}}
     *                        {{remark.DATA}}
     */
    public void sendBindSuccessMsg(@NotNull String msgTitle, @Nonnull String deviceSn, @Nullable String msgTime, @Nonnull String... receiverOpenIds) {
        WechatTemplateParams data = WechatTemplateParams.WechatTemplateParamsBuilder.initTemplateParams()
                .withFirst(msgTitle)
                .withKeyword1(deviceSn)
                .withKeyword2(msgTime)
                .build();
        sendTemplateMsg(TemplateOthers.BIND_SUCCESS.getTemplateId(), null, data, receiverOpenIds);
    }


    /**
     * 发送模板消息,详情打开H5页面
     *
     * @param templateId
     * @param url
     * @param data
     * @param receiverOpenIds
     */
    private void sendTemplateMsg(@Nonnull String templateId, @Nullable String url, WechatTemplateParams data, @Nonnull String... receiverOpenIds) {
        if (receiverOpenIds.length == 0) {
            log.info("WechatApi.sendTemplateMsg.[templateId, url, data, receiverOpenIds is empty ");
            // 接受者为空 中断
            return;
        }
        String accessToken = getOfficialAccountAccessToken();
        if (accessToken == null) {
            log.info("WechatApi.sendTemplateMsg.[templateId, url, data, receiverOpenIds] get accesstoken failure ");
            return;
        }
        WechatTemplate templateInfo = new WechatTemplate(templateId, data);
        templateInfo.setUrl(url);
        for (String openId : receiverOpenIds) {
            templateInfo.setTouser(openId);
            wechatOfficialAccountApi.sendTemplateMsg(accessToken, templateInfo);
        }
    }

    /**
     * 发送模板消息，详情打开小程序页面
     *
     * @param templateId
     * @param miniprogram
     * @param data
     * @param receiverOpenIds
     */
    private void sendTemplateMsgToXcx(@Nonnull String templateId, WechatTemplateMiniProgram miniprogram, @Nonnull WechatTemplateParams data, @Nonnull String... receiverOpenIds) {
        if (receiverOpenIds.length == 0) {
            // 接受者为空 中断
            return;
        }
        String accessToken = getOfficialAccountAccessToken();
        if (accessToken == null) {
            return;
        }
        WechatTemplate templateInfo = new WechatTemplate(templateId, data);
        templateInfo.setMiniprogram(miniprogram);
        for (String openId : receiverOpenIds) {
            templateInfo.setTouser(openId);
            wechatOfficialAccountApi.sendTemplateMsg(accessToken, templateInfo);
        }
    }

    // endregion ========================== 模板消息 ==============================================

    // region ================================== 标签与菜单 ===========================================================

    public WechatCreateTagJSON createWechatTag(String tagName) {
        return wechatOfficialAccountApi.createWechatTag(getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret()), tagName);
    }

    public WxTagListResponse listWechatTag() {
        return wechatOfficialAccountApi.listWechatTag(getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret()));
    }

    public WechatResponse bindingTag(List<String> openids, Integer tagid, List<Integer> mutexTagids) {
        AccessToken accessToken = getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret());
        for (String openid : openids) {
            JSONObject jsonObject = wechatOfficialAccountApi.queryUserTag(accessToken, openid);
            JSONArray tagidList = jsonObject.getJSONArray("tagid_list");
            if (!CollectionUtils.isEmpty(tagidList)) {
                boolean anyMatch = tagidList.stream().anyMatch(tagId -> mutexTagids.stream().anyMatch(mt -> mt.equals(tagId)));
                if (anyMatch) {
                    // 匹配 互斥ID，不继续进行标签设置
                    return null;
                }
            }
        }
        return wechatOfficialAccountApi.bindingTag(accessToken, openids, tagid);
    }

    public WechatResponse bindingTag(Integer tagid, List<Integer> mutexTagids, String... openids) {
        List<String> list = Arrays.stream(openids).collect(Collectors.toList());
        log.info("WechatApi.bindingTag.[tagid, openids] list =" + list);
        return bindingTag(list, tagid, mutexTagids);
    }

    public WxCreateIndividuationResponse createIndividuationMenu(WechatMenu menu) {
        return wechatOfficialAccountApi.createConditionalMenu(getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret()), menu);
    }

    public WechatResponse createCommonMenu(WechatMenu menu) {
        return wechatOfficialAccountApi.createCommonMenu(getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret()), menu);
    }

    public WechatResponse removeMenu() {
        return wechatOfficialAccountApi.removeMenu(getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret()));
    }

    /**
     * 查询公众号菜单
     *
     * @param appId
     * @param appSecret
     * @return
     */
    public JSONObject queryOfficeAccountMenu(String appId, String appSecret) {
        if (appId == null) {
            appId = WechatUtil.getNiTangConfig().getAppId();
        }
        if (appSecret == null) {
            appSecret = WechatUtil.getNiTangConfig().getAppSecret();
        }
        return wechatOfficialAccountApi.queryOfficeAccountMenu(getAccessToken(appId, appSecret));
    }

    // endregion ================================= 标签与菜单 =====================================================

    /**
     * 创建用户二维码
     *
     * @param userId 用户ID
     * @param others 其他参数
     * @return
     */
    public WechatQrCodeTicket createUserQrCode(@NotNull Integer userId, @NotNull Integer qrcodeType, @Nullable String others) {
        WechatOaQrCodeScene scene = new WechatOaQrCodeScene();
        scene.setUserId(userId);
        scene.setOthers(others);
        scene.setQrCodeType(qrcodeType);
        return createPermanentQrCode(userId + "", JSONObject.toJSONString(scene));
    }

    /**
     * 创建永久二维码
     *
     * @param content
     * @return
     */
    private WechatQrCodeTicket createPermanentQrCode(String sceneId, String content) {
        return createQrCode(sceneId, content, 0);
    }

    /**
     * 创建字符串内容的临时微信二维码, 默认1小时
     *
     * @param content 二维码内容
     * @return 二维码图片地址
     */
    private WechatQrCodeTicket createTemporaryQrCode(String sceneId, String content) {
        return createQrCode(sceneId, content, 3600 * 24);
    }

    /**
     * 创建带参数二维码
     *
     * @param content
     * @param expireSeconds 二维码有效期  <=0 为永久二维码
     * @return
     */
    private WechatQrCodeTicket createQrCode(String sceneId, String content, Integer expireSeconds) {
        return wechatOfficialAccountApi.createQrCode(getAccessToken(WechatUtil.getNiTangConfig().getAppId(), WechatUtil.getNiTangConfig().getAppSecret()).getAccessToken(), sceneId, content, expireSeconds);
    }


    //endregion================================  公众号相关接口 结束  ================================

    //region================================  企业微信相关接口 开始  ================================

    //endregion ================================  企业微信相关接口 结束  ================================

    //region ================================  微信支付相关接口 开始 ================================
    public String prePay(@NotNull PayScene scene, Integer userId, String openId, String outTradeNo, Long total, String description, SceneInfo sceneInfo, HttpServletRequest request) {
        if (sceneInfo != null) {
            try {
                //获取request
                //设置IP地址
                sceneInfo.setPayer_client_ip(IPUtils.getIpAddr(request));
            } catch (Exception e) {
                sceneInfo.setPayer_client_ip("127.0.0.1");
                e.printStackTrace();
            }
        }
        switch (scene) {
            case H5:
                return wechatPayApi.h5PrePay(total, userId, outTradeNo, description, scene.getName(), sceneInfo);
            case APP:
                return wechatPayApi.appPrePay(total, userId, outTradeNo, description, scene.getName());
            case JSAPI:
                String entity = wechatPayApi.jsPrePay(total, userId, openId, outTradeNo, description, scene.getName());
                JSONObject jsonObject = JSONObject.parseObject(entity);
                Object prepay_id = jsonObject.get("prepay_id");
                if (prepay_id == null) {
                    return null;
                }
                String nonceStr = generateNonceStr();
                long timestamp = System.currentTimeMillis() / 1000L;
                HashMap<String, String> payMap = new HashMap<>();
                try {
                    payMap.put("appId", WechatUtil.getClientId());
                    payMap.put("timeStamp", timestamp + "");
                    payMap.put("nonceStr", nonceStr);
                    payMap.put("signType", "RSA");
                    payMap.put("package", "prepay_id=" + prepay_id);
                    String paySign = structurePaySign(payMap, PayConfig.getMerchantPrivateKey());
                    payMap.put("paySign", paySign);
                    payMap.put("orderNo", outTradeNo);
                } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | IOException e) {
                    e.printStackTrace();
                }
                return JSONUtil.toJsonStr(payMap);
            case NATIVE:
                return wechatPayApi.nativePrePay(total, userId, outTradeNo, description, scene.getName(), sceneInfo);
            default:
                log.info("WechatApi.prePay.[scene] default do nothing ");
                break;
        }
        return "";
    }

    private static final String SIGNTYPE = "SHA256withRSA";

    public static String structurePaySign(Map<String, String> payData, PrivateKey privateKey) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        String message = payData.get("appId") + "\n" + payData.get("timeStamp") + "\n" + payData.get("nonceStr") + "\n" + payData.get("package") + "\n";
        Signature signature = Signature.getInstance(SIGNTYPE);
        signature.initSign(privateKey);
        signature.update(message.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(signature.sign());
    }

    public String buildMessage(String nonce, long timestamp, HttpServletRequest request, String aPackage) throws IOException {
        String canonicalUrl = request.getRequestURI();
        if (request.getQueryString() != null) {
            canonicalUrl = canonicalUrl + "?" + request.getQueryString();
        }
        return request.getMethod() + "\n" + canonicalUrl + "\n" + timestamp + "\n" + nonce + "\n" + aPackage + "\n";
    }

    protected static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    protected static final SecureRandom RANDOM = new SecureRandom();

    protected String generateNonceStr() {
        char[] nonceChars = new char[32];

        for (int index = 0; index < nonceChars.length; ++index) {
            nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
        }

        return new String(nonceChars);
    }

    //endregion ================================  微信支付相关接口 结束  ================================
}
